/*
 * Testing tool for ASN.1/X.509v3 routines
 * Copyright (c) 2006, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include "includes.h"

#include "common.h"
#include "asn1.h"
#include "x509v3.h"

extern int wpa_debug_level;


static const char * asn1_class_str(int class)
{
        switch (class) {
        case ASN1_CLASS_UNIVERSAL:
                return "Universal";
        case ASN1_CLASS_APPLICATION:
                return "Application";
        case ASN1_CLASS_CONTEXT_SPECIFIC:
                return "Context-specific";
        case ASN1_CLASS_PRIVATE:
                return "Private";
        default:
                return "?";
        }
}


int asn1_parse(const u8 *buf, size_t len, int level)
{
        const u8 *pos, *prev, *end;
        char prefix[10], str[100];
        int _level;
        struct asn1_hdr hdr;
        struct asn1_oid oid;
        u8 tmp;

        _level = level;
        if ((size_t) _level > sizeof(prefix) - 1)
                _level = sizeof(prefix) - 1;
        memset(prefix, ' ', _level);
        prefix[_level] = '\0';

        pos = buf;
        end = buf + len;

        while (pos < end) {
                if (asn1_get_next(pos, end - pos, &hdr) < 0)
                        return -1;

                prev = pos;
                pos = hdr.payload;

                wpa_printf(MSG_MSGDUMP, "ASN.1:%s Class %d(%s) P/C %d(%s) "
                           "Tag %u Length %u",
                           prefix, hdr.class, asn1_class_str(hdr.class),
                           hdr.constructed,
                           hdr.constructed ? "Constructed" : "Primitive",
                           hdr.tag, hdr.length);

                if (hdr.class == ASN1_CLASS_CONTEXT_SPECIFIC &&
                    hdr.constructed) {
                        if (asn1_parse(pos, hdr.length, level + 1) < 0)
                                return -1;
                        pos += hdr.length;
                }

                if (hdr.class != ASN1_CLASS_UNIVERSAL)
                        continue;

                switch (hdr.tag) {
                case ASN1_TAG_EOC:
                        if (hdr.length) {
                                wpa_printf(MSG_DEBUG, "ASN.1: Non-zero "
                                           "end-of-contents length (%u)",
                                           hdr.length);
                                return -1;
                        }
                        wpa_printf(MSG_MSGDUMP, "ASN.1:%s EOC", prefix);
                        break;
                case ASN1_TAG_BOOLEAN:
                        if (hdr.length != 1) {
                                wpa_printf(MSG_DEBUG, "ASN.1: Unexpected "
                                           "Boolean length (%u)", hdr.length);
                                return -1;
                        }
                        tmp = *pos++;
                        wpa_printf(MSG_MSGDUMP, "ASN.1:%s Boolean %s",
                                   prefix, tmp ? "TRUE" : "FALSE");
                        break;
                case ASN1_TAG_INTEGER:
                        wpa_hexdump(MSG_MSGDUMP, "ASN.1: INTEGER",
                                    pos, hdr.length);
                        pos += hdr.length;
                        break;
                case ASN1_TAG_BITSTRING:
                        wpa_hexdump(MSG_MSGDUMP, "ASN.1: BitString",
                                    pos, hdr.length);
                        pos += hdr.length;
                        break;
                case ASN1_TAG_OCTETSTRING:
                        wpa_hexdump(MSG_MSGDUMP, "ASN.1: OctetString",
                                    pos, hdr.length);
                        pos += hdr.length;
                        break;
                case ASN1_TAG_NULL:
                        if (hdr.length) {
                                wpa_printf(MSG_DEBUG, "ASN.1: Non-zero Null "
                                           "length (%u)", hdr.length);
                                return -1;
                        }
                        wpa_printf(MSG_MSGDUMP, "ASN.1:%s Null", prefix);
                        break;
                case ASN1_TAG_OID:
                        if (asn1_get_oid(prev, end - prev, &oid, &prev) < 0) {
                                wpa_printf(MSG_DEBUG, "ASN.1: Invalid OID");
                                return -1;
                        }
                        asn1_oid_to_str(&oid, str, sizeof(str));
                        wpa_printf(MSG_DEBUG, "ASN.1:%s OID %s", prefix, str);
                        pos += hdr.length;
                        break;
                case ANS1_TAG_RELATIVE_OID:
                        wpa_hexdump(MSG_MSGDUMP, "ASN.1: Relative OID",
                                    pos, hdr.length);
                        pos += hdr.length;
                        break;
                case ASN1_TAG_SEQUENCE:
                        wpa_printf(MSG_MSGDUMP, "ASN.1:%s SEQUENCE", prefix);
                        if (asn1_parse(pos, hdr.length, level + 1) < 0)
                                return -1;
                        pos += hdr.length;
                        break;
                case ASN1_TAG_SET:
                        wpa_printf(MSG_MSGDUMP, "ASN.1:%s SET", prefix);
                        if (asn1_parse(pos, hdr.length, level + 1) < 0)
                                return -1;
                        pos += hdr.length;
                        break;
                case ASN1_TAG_PRINTABLESTRING:
                        wpa_hexdump_ascii(MSG_MSGDUMP,
                                          "ASN.1: PrintableString",
                                          pos, hdr.length);
                        pos += hdr.length;
                        break;
                case ASN1_TAG_IA5STRING:
                        wpa_hexdump_ascii(MSG_MSGDUMP, "ASN.1: IA5String",
                                          pos, hdr.length);
                        pos += hdr.length;
                        break;
                case ASN1_TAG_UTCTIME:
                        wpa_hexdump_ascii(MSG_MSGDUMP, "ASN.1: UTCTIME",
                                          pos, hdr.length);
                        pos += hdr.length;
                        break;
                case ASN1_TAG_VISIBLESTRING:
                        wpa_hexdump_ascii(MSG_MSGDUMP, "ASN.1: VisibleString",
                                          pos, hdr.length);
                        pos += hdr.length;
                        break;
                default:
                        wpa_printf(MSG_DEBUG, "ASN.1: Unknown tag %d",
                                   hdr.tag);
                        return -1;
                }
        }

        return 0;
}


int main(int argc, char *argv[])
{
        FILE *f;
        u8 buf[3000];
        size_t len;
        struct x509_certificate *cert;

        wpa_debug_level = 0;

        f = fopen(argv[1], "rb");
        if (f == NULL)
                return -1;
        len = fread(buf, 1, sizeof(buf), f);
        fclose(f);

        if (asn1_parse(buf, len, 0) < 0)
                printf("Failed to parse DER ASN.1\n");

        printf("\n\n");

        cert = x509_certificate_parse(buf, len);
        if (cert == NULL)
                printf("Failed to parse X.509 certificate\n");
        x509_certificate_free(cert);

        return 0;
}
